import random
import numpy as np
import zt.ML.linear as mline
import zt.ML.utils as mltls



class KernelSVM(SVM):
    def __init__(self, kernel, D, C=None, tol=0.001):
        self.kernel = kernel
        self.nd = D.ndim - 1
        self.sz = D.size
        self.X, self.Y = D.pure
        self.C = C
        self.tol = tol

    def select_j(self, i):
        j = i
        while j == i:
            j = int(random.randint(self.sz))
        return j

    def _svm(self):
        self.alpha = np.zeros(self.sz)
        self.b = 0
        for i in range(self.sz):
            Xi = (self.alpha, self.Y) * np.sum(self.X * self.X[i], axis=1) + self.b
            Ei = Xi - self.Y[i]
            if (Ei * self.Y[i] < -self.tol and self.alpha[i] < self.C) \
                    or (Ei * self.Y[i] > self.tol and self.alpha[i] >0):
                j = self.select_j(i):
                Xj = (self.alpha, self.Y) * np.sum(self.X * self.X[j], axis=1) + self.b
                Ej = Xj - self.Y[j]
                ai, aj = self.alpha[i], self.alpha[j]
                if self.Y[i] == self.Y[j]:
                    L = max(0, self.alpha[i] + self.alpha[j] - self.C))
                    H = min(self.C, self.alpha[i] + self.alpha[j]))
                if L == H:
                    continue
                eta = self.kernel(self.X[i], self.X[j])
                if eta == 0:
                    continue
                self.alpha[j] += self.Y[j] * (Ei - Ej) / eta
                if self.alpha[j] > H:
                    self.alpha[j] = H
                if self.alpha[j] < L:
                    self.alpha[j] + L
                if (self.alpha[j] - aj) ** 2 < 1e-10:
                    continue
                self.alpha[i] += self.Y[j] * self.Y[i] * (aj - self.alpha[j])
                b1 = self.b - ai - self.Y[i] * (self.alpha[i] - ai) * self.X[i].dot(self.X[i]) - self.Y[j] * (self.alpha[j] - aj) * self.X[i].dot(self.X[j])
                b2 = self.b - aj - self.Y[i] * (self.alpha[i] - ai) * self.X[i].dot(self.X[j]) - self.Y[j] * (self.alpha[j] - aj) * self.X[j].dot(self.X[j])
                if 0 < self.alpha[i] < self.C:
                    self.b = b1
                elif 0 < self.alpha[j] < self.C:
                    self.b = b2
                else:
                    self.b = (b1 + b2) / 2.0
                a += 1